﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Perforce.P4;

namespace Red.Core.Extensions
{
    public static class P4Api
    {
        /// <summary>
        /// Returns a list of all streams available in a perforce repository
        /// </summary>
        /// <returns>List of all streams</returns>
        public static IList<Stream> GetStreams( this Repository repository, params string[] streamIds )
        {
            P4Command cmd = new P4Command( repository, "streams", true, streamIds );
            P4CommandResult results = cmd.Run();
            if ( results.Success )
            {
                if ( ( results.TaggedOutput == null ) || ( results.TaggedOutput.Count <= 0 ) )
                {
                    return null;
                }
                List<Stream> value = new List<Stream>();

                bool dst_mismatch = false;
                string offset = string.Empty;

                if ( repository.Server != null && repository.Server.Metadata != null )
                {
                    offset = repository.Server.Metadata.DateTimeOffset;
                    dst_mismatch = FormBase.DSTMismatch( repository.Server.Metadata );
                }

                foreach ( TaggedObject obj in results.TaggedOutput )
                {
                    Stream stream = new Stream();
                    stream.FromStreamsCmdTaggedOutput( obj, offset, dst_mismatch );
                    value.Add( stream );
                }
                return value;
            }
            else
            {
                foreach ( var error in results.ErrorList )
                {
                    // Presumably this will stop executing here, should I manually combine each error before passing it to the exception?
                    P4Exception.Throw( error );
                }
            }
            return null;
        }

        public static ulong GetPathSize( this Repository repository, string path )
        {
            P4Command cmd = new P4Command( repository, "sizes", true, path );
            Options options = new Options();
            options[ "-s" ] = null;

            P4CommandResult results = cmd.Run( options );

            return Convert.ToUInt64( results.TaggedOutput[ 0 ][ "fileSize" ] );
        }

        public static ulong GetStreamSize( this Repository repository, string stream )
        {
            return repository.GetStreamSize( stream, new List<MapEntry>() );
        }

        public static ulong GetStreamSize( this Repository repository, string stream, ICollection<MapEntry> examinedPaths )
        {
            Stream streamObj = repository.GetStream( stream );

            Debug.Print( $"Getting size of stream: {stream}" );

            ulong totalSize = 0;
            string basePath;

            if ( streamObj.Type == StreamType.Virtual )
            {
                // Virtual paths return a size of 0, so we need to modify the paths to use their parent's stream
                basePath = streamObj.BaseParent.Path;

                // If it's a virtual path, we'll also want to recurse down the tree and accumulate the sizes of our ancestors too
                totalSize += repository.GetStreamSize( streamObj.Parent.Path, examinedPaths );
            }
            else
            {
                basePath = streamObj.Id;
            }

            // Iterate over each path mapping specified for this stream
            foreach ( MapEntry entry in streamObj.Paths )
            {
                if ( !examinedPaths.Contains( entry ) )
                {
                    bool previouslyIncluded = IsPathIncluded( examinedPaths, entry.Left.Path );

                    if ( entry.Type == MapType.Exclude || entry.Type == MapType.StreamPathExclude )
                    {
                        if ( previouslyIncluded )
                        {
                            // Construct the absolute perforce path
                            string path = $"{basePath}/{entry.Left.Path}";

                            // Request the size of the path from perforce
                            ulong size = repository.GetPathSize( path );

                            totalSize -= size;

                            MapEntry reverse = new MapEntry( MapType.Share, entry.Left, entry.Right );
                            examinedPaths.Remove( reverse );

                            Debug.Print( $"Subtracting '{path}': {size} ({size / ( 1024.0 * 1024 ):N2}MB) (Total: {totalSize / ( 1024.0 * 1024 ):N2}MB)" );
                        }
                    }
                    else
                    {
                        if ( !previouslyIncluded )
                        {
                            // Construct the absolute perforce path
                            //string path = string.IsNullOrEmpty( entry.Right.Path ) ? $"{basePath}/{entry.Left.Path}" : $"{basePath}/{entry.Right.Path}";

                            // Construct the absolute perforce path
                            string path = $"{basePath}/{entry.Left.Path}";

                            // Request the size of the path from perforce
                            ulong size = repository.GetPathSize( path );

                            totalSize += size;

                            Debug.Print( $"Adding '{path}': {size} ({size / ( 1024.0 * 1024 ):N2}MB) (Total: {totalSize / ( 1024.0 * 1024 ):N2}MB)" );
                        }
                    }

                    examinedPaths.Add( entry );
                }
            }

            return totalSize;
        }

        private static bool IsSubPathIncluded( ICollection<MapEntry> examinedPaths, params string[] paths )
        {
            foreach ( MapEntry entry in examinedPaths )
            {
                if ( !( entry.Type == MapType.Exclude || entry.Type == MapType.StreamPathExclude ) )
                {
                    foreach ( string path in paths )
                    {
                        if ( entry.Left.Path == path )
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private static bool IsPathIncluded( ICollection<MapEntry> examinedPaths, string path )
        {
            Debug.Print( $"IsPathIncluded -> {path}" );

            int directoriesCount = path.Count( c => c == '/' );

            string[] dirs = new string[ directoriesCount + 2 ];

            string editedPath = path;
            for( int i = 0; i < directoriesCount; ++i )
            {
                int index = editedPath.LastIndexOf( '/' );
                editedPath = editedPath.Substring( 0, index );

                dirs[ i ] = editedPath + "/...";
            }

            dirs[ directoriesCount ] = "...";
            dirs[ directoriesCount + 1 ] = path;

            return IsSubPathIncluded( examinedPaths, dirs );
        }
    }
}
